﻿using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;

namespace Microsoft.CTS.HSG
{
    public class ScriptEngine
    {
        public event EventHandler<ScriptingErrorEventArgs> ScriptError;
        public event EventHandler<ProgressReportEventArgs> ScriptProgress;

        public ScriptEngine()
        {
            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "ENTER: Constructor/ScriptEngine.ScriptEngine"
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "EXIT: Constructor/ScriptEngine.ScriptEngine"
                );
        }
        /// <summary>
        /// Generates a database script.
        /// </summary>
        /// <param name="config"></param>
        public void GenerateScript(
            ScriptEngineConfiguration config
            )
        {
            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "ENTER: Method/ScriptEngine.GenerateScript"
                );

            ScriptEngineConfiguration.ValidateConfiguration(
                config
                );

            Debug.Assert(
                config != null,
                "ScriptEngineConfiguration instance passed to GenerateScript was null after call to ValidateConfiguration!"
                );

            Server smoServer = ScriptEngine.CreateSmoServer(
                config.Instance
                );

            Database smoDatabase = smoServer.Databases[config.Database];
            Urn smoDatabaseUrn = smoServer.Databases[config.Database].Urn;

            ScriptingOptions smoScriptingOptions = ScriptEngine.GetSmoScriptingOptions(
                config
                );

            //
            // Script the database create before scripting objects in the database.
            //

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "Scripting database '{0}'.",
                smoDatabase.Name
                );

            this.ScriptDatabase(
                smoServer,
                smoDatabaseUrn,
                config,
                smoScriptingOptions
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "Finished scripting database '{0}'.",
                smoDatabase.Name
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "Scripting objects in database '{0}'.",
                smoDatabase.Name
                );

            this.ScriptObjects(
                smoDatabase,
                config,
                smoScriptingOptions
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "Finished scripting objects in database '{0}'.",
                smoDatabase.Name
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "EXIT: Method/ScriptEngine.GenerateScript"
                );
        }

        /// <summary>
        /// Scripts objects contained in a database and server logins.
        /// </summary>
        /// <param name="smoDatabase"></param>
        /// <param name="config"></param>
        /// <param name="options"></param>
        private void ScriptObjects(
            Database smoDatabase,
            ScriptEngineConfiguration config,
            ScriptingOptions options
            )
        {
            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "ENTER: Method/ScriptEngine.ScriptObjects"
                );

            Debug.Assert(
                smoDatabase != null,
                "smoDatabase passed to ScriptObjects in null!"
                );

            Debug.Assert(
                config != null,
                "config passed to ScriptObjects in null!"
                );

            Debug.Assert(
                string.IsNullOrEmpty(config.Database) == false,
                "ScriptEngineConfiguration.Database passed to ScriptObjects is null or empty!"
                );

            Debug.Assert(
                options != null,
                "options passed to ScriptObjects in null!"
                );

            Transfer transfer = new Transfer(
                smoDatabase
                );

            transfer.Options = options;

            //
            //Set AppendToFile to true so that it adds to the script started by ScriptDatabase()
            //

            transfer.Options.AppendToFile = true;

            //
            //Since the Transfer class requires a destination database, specify a bogus one. Nothing will actually be copied since
            //ScriptOptions.ToFileOnly is always true
            //

            transfer.DestinationDatabase = "blah";
            transfer.CopyAllObjects = config.CopyAllObjects;
            transfer.CopyAllLogins = config.CopyAllLogins;
            transfer.ScriptingProgress += this.OnScriptingProgress;
            transfer.ScriptingError += this.OnScriptingError;
            transfer.ScriptTransfer();

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "EXIT: Method/ScriptEngine.ScriptObjects"
                );
        }

        /// <summary>
        /// Scripts database creation.
        /// </summary>
        /// <param name="smoServer"></param>
        /// <param name="smoDatabaseUrn"></param>
        /// <param name="config"></param>
        /// <param name="options"></param>
        private void ScriptDatabase(
            Server smoServer,
            Urn smoDatabaseUrn,
            ScriptEngineConfiguration config,
            ScriptingOptions options
            )
        {

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "ENTER: Method/ScriptEngine.ScriptDatabase"
                );

            Debug.Assert(
                smoServer != null,
                "smoServer passed to ScriptDatabase in null!"
                );

            Debug.Assert(
                smoDatabaseUrn != null,
                "smoDatabaseUrn passed to ScriptDatabase in null!"
                );

            Debug.Assert(
                config != null,
                "config passed to ScriptDatabase in null!"
                );

            Debug.Assert(
                string.IsNullOrEmpty(config.Database) == false,
                "ScriptEngineConfiguration.Database passed to ScriptDatabase is null or empty!"
                );

            Debug.Assert(
                options != null,
                "options passed to ScriptDatabase in null!"
                );

            Scripter scripter = new Scripter(
                smoServer
                );

            scripter.Options = ScriptEngine.GetSmoScriptingOptions(
                config
                );

            //
            // Need to set WithDependenices to false since this is not allowed when scripting a database.
            //

            scripter.Options.WithDependencies = false;

            //
            // Full-text objects will be handled by ScriptObjects()
            //

            scripter.Options.FullTextCatalogs = false;
           

            scripter.ScriptingProgress += new ProgressReportEventHandler(OnScriptingProgress);
            scripter.ScriptingError += new ScriptingErrorEventHandler(OnScriptingError);

            scripter.Script(
                new Urn[] { smoDatabaseUrn }
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "EXIT: Method/ScriptEngine.ScriptDatabase"
                );

        }

        private static Server CreateSmoServer(
           string instance
           )
        {
            Debug.Assert(
                string.IsNullOrEmpty(instance) == false,
                "instance passed to CreateSmoServer is null or empty!"
                );

            return new Server(
                instance
                );
        }


        private void OnScriptingError(
            object sender, 
            ScriptingErrorEventArgs e
            )
        {
            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "ENTER: EventHandler/ScriptEngine.OnScriptingError"
                );

            EventHandler<ScriptingErrorEventArgs> copy = this.ScriptError;

            if (copy != null)
            {
                SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "OnProgress;Urn:{0};InnerException:{1}",
                e.Current,
                e.InnerException.StackTrace
                );

                SqlScripterDiagnostics.Log(
                TraceLevel.Error,
                "Error scripting object {0};Error:{1}",
                e.Current,
                e.InnerException.Message
                );

                this.ScriptError(
                    sender,
                    e
                    );
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "EXIT: EventHandler/ScriptEngine.OnScriptingError"
                );
        }

        private void OnScriptingProgress(
            object sender, 
            ProgressReportEventArgs e
            )
        {

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "ENTER: EventHandler/ScriptEngine.OnScriptingProgress"
                );

            EventHandler<ProgressReportEventArgs> copy = this.ScriptProgress;

            if (copy != null)
            {
                SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "OnProgress;Urn:{0};Parent:{1};SubTotal:{2};SubTotalCount:{3};Total:{4};TotalCount:{5}",
                e.Current,
                e.Parent,
                e.SubTotal,
                e.SubTotalCount,
                e.Total,
                e.TotalCount
                );

                SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "Scripting object {0}",
                e.Current
                );

                this.ScriptProgress(
                    sender,
                    e
                    );
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "EXIT: EventHandler/ScriptEngine.OnScriptingProgress"
                );
        }

        /// <summary>
        /// Converts a ScriptEngineConfiguration instance to an SMO ScriptingOptions instance.
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        private static ScriptingOptions GetSmoScriptingOptions(
            ScriptEngineConfiguration config
            )
        {
            Debug.Assert(
                config != null,
                "ScriptEngineConfiguration passed to GetSmoScriptingOptions was null!"
                );

            Debug.Assert(
                string.IsNullOrEmpty(config.Instance) == false,
                "ScriptEngineConfiguration.Instance was null!"
                );

            ScriptingOptions scriptingOptions = new ScriptingOptions();

            scriptingOptions.AgentAlertJob = config.AgentAlertJob;
            scriptingOptions.AgentJobId = config.AgentJobId;
            scriptingOptions.AgentNotify = config.AgentNotify;
            scriptingOptions.AllowSystemObjects = config.AllowSystemObjects;
            scriptingOptions.AnsiFile = config.AnsiFile;
            scriptingOptions.AnsiPadding = config.AnsiPadding;
            scriptingOptions.AppendToFile = config.AppendToFile;
            scriptingOptions.BatchSize = config.BatchSize;
            scriptingOptions.Bindings = config.Bindings;
            scriptingOptions.ChangeTracking = config.ChangeTracking;
            scriptingOptions.ClusteredIndexes = config.ClusteredIndexes;
            scriptingOptions.ContinueScriptingOnError = config.ContinueScriptingOnError;
            scriptingOptions.ConvertUserDefinedDataTypesToBaseType = config.ConvertUserDefinedDataTypesToBaseType;
            scriptingOptions.DdlBodyOnly = config.DdlBodyOnly;
            scriptingOptions.DdlHeaderOnly = config.DdlHeaderOnly;
            scriptingOptions.Default = config.Default;
            scriptingOptions.DriAll = config.DriAll;
            scriptingOptions.DriAllConstraints = config.DriAllConstraints;
            scriptingOptions.DriAllKeys = config.DriAllKeys;
            scriptingOptions.DriChecks = config.DriChecks;
            scriptingOptions.DriClustered = config.DriClustered;
            scriptingOptions.DriDefaults = config.DriDefaults;
            scriptingOptions.DriForeignKeys = config.DriForeignKeys;
            scriptingOptions.DriIncludeSystemNames = config.DriIncludeSystemNames;
            scriptingOptions.DriIndexes = config.DriIndexes;
            scriptingOptions.DriNonClustered = config.DriNonClustered;
            scriptingOptions.DriPrimaryKey = config.DriPrimaryKey;
            scriptingOptions.DriUniqueKeys = config.DriUniqueKeys;
            scriptingOptions.DriWithNoCheck = config.DriWithNoCheck;
            scriptingOptions.Encoding = config.Encoding;
            scriptingOptions.EnforceScriptingOptions = config.EnforceScriptingOptions;
            scriptingOptions.ExtendedProperties = config.ExtendedProperties;
            scriptingOptions.FileName = config.FileName;
            scriptingOptions.FullTextCatalogs = config.FullTextCatalogs;
            scriptingOptions.FullTextIndexes = config.FullTextIndexes;
            scriptingOptions.FullTextStopLists = config.FullTextStopLists;
            scriptingOptions.IncludeDatabaseContext = config.IncludeDatabaseContext;
            scriptingOptions.IncludeDatabaseRoleMemberships = config.IncludeDatabaseRoleMemberships;
            scriptingOptions.IncludeFullTextCatalogRootPath = config.IncludeFullTextCatalogRootPath;
            scriptingOptions.IncludeHeaders = config.IncludeHeaders;
            scriptingOptions.IncludeIfNotExists = config.IncludeIfNotExists;
            scriptingOptions.Indexes = config.Indexes;
            scriptingOptions.LoginSid = config.LoginSid;
            scriptingOptions.NoAssemblies = !config.Assemblies;
            scriptingOptions.NoCollation = !config.Collation;
            scriptingOptions.NoCommandTerminator = !config.CommandTerminator;
            scriptingOptions.NoExecuteAs = !config.ExecuteAs;
            scriptingOptions.NoFileGroup = !config.FileGroup;
            scriptingOptions.NoFileStream = !config.FileStream;
            scriptingOptions.NoFileStreamColumn = !config.FileStreamColumn;
            scriptingOptions.NoIdentities = !config.Identities;
            scriptingOptions.NoIndexPartitioningSchemes = !config.IndexPartitioningSchemes;
            scriptingOptions.NoMailProfileAccounts = !config.MailProfileAccounts;
            scriptingOptions.NoMailProfilePrincipals = !config.MailProfilePrincipals;
            scriptingOptions.NonClusteredIndexes = config.NonClusteredIndexes;
            scriptingOptions.NoTablePartitioningSchemes = !config.TablePartitioningSchemes;
            scriptingOptions.NoVardecimal = !config.Vardecimal;
            scriptingOptions.NoViewColumns = !config.ViewColumns;
            scriptingOptions.NoXmlNamespaces = !config.XmlNamespaces;
            scriptingOptions.OptimizerData = config.OptimizerData;
            scriptingOptions.Permissions = config.Permissions;
            scriptingOptions.PrimaryObject = config.PrimaryObject;
            scriptingOptions.SchemaQualify = config.SchemaQualify;
            scriptingOptions.SchemaQualifyForeignKeysReferences = config.SchemaQualifyForeignKeysReferences;
            scriptingOptions.ScriptBatchTerminator = config.ScriptBatchTerminator;
            scriptingOptions.ScriptData = config.ScriptData;
            scriptingOptions.ScriptDataCompression = config.ScriptDataCompression;
            scriptingOptions.ScriptDrops = config.ScriptDrops;
            scriptingOptions.ScriptOwner = config.ScriptOwner;
            scriptingOptions.ScriptSchema = config.ScriptSchema;
            scriptingOptions.Statistics = config.Statistics;
            scriptingOptions.TargetServerVersion = config.TargetServerVersion;
            scriptingOptions.TimestampToBinary = config.TimestampToBinary;
            scriptingOptions.ToFileOnly = config.ToFileOnly;
            scriptingOptions.Triggers = config.Triggers;
            scriptingOptions.WithDependencies = config.WithDependencies;
            scriptingOptions.XmlIndexes = config.XmlIndexes;

            return scriptingOptions;
        }
    }
}
